/**
 * Copyright (c) 2010 Basil Shikin, LegoCamera Project
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.legocam;

import lejos.nxt.LCD;

import org.legocam.actions.LoadSettings;
import org.legocam.actions.PromptFilmProperties;
import org.legocam.actions.SaveSettings;
import org.legocam.actions.TakePicture;
import org.legocam.actions.WindFilm;

/**
 * This class represents a camera. It contains logic for all operations.
 * 
 * @author Basil Shikin
 *
 */
public class Camera
{
	private static final int F_STOP = 152; // Current F-stop
	private static final int SHUTTER_ANGLE = 90; // Angle to rotate shutter motor
	
	// Basic action
	private final Runnable windFilm;
	private final Runnable promptFilmProperties;
	private final Runnable takeSnapshot;
	private final Runnable saveSettings;
	private final Runnable loadSettings;

	// Camera state
	private boolean cameraRunning = true;
	private boolean isBodyInstalled = false;
	
	// Film state
	private int snapshotsLeft = 0;
	private int filmSize = 24;
	private int isoValue = 100;
	
	// Exposure state
	private int ev = 0;
	private long shutterTime = 0;
	
	public Camera()
	{
		this.promptFilmProperties = new PromptFilmProperties( this );
		this.windFilm = new WindFilm( this );
		this.takeSnapshot = new TakePicture( this );
		this.saveSettings = new SaveSettings( this );
		this.loadSettings = new LoadSettings( this );
	}
	
	public boolean isRunning()
	{
		return cameraRunning;
	}
	
	public void stop()
	{
		cameraRunning = false;
	}

	public void setLightValue(int rawLightValue)
	{
		ev = ExposureUtils.getEVValue( rawLightValue );
		final double timeSeconds = ExposureUtils.calculateShutterTime( ev, F_STOP, isoValue );
		shutterTime = Math.round( timeSeconds * 1000 );
		
		printHeaderData( ev, isoValue );
		printFooterData( F_STOP, timeSeconds, snapshotsLeft );
	}


	public void setBodyInstalled(boolean isInstalled)
	{
		boolean wasInstalled = isBodyInstalled;
		isBodyInstalled = isInstalled;
		
		if ( isInstalled && !wasInstalled )
		{
			promptFilmProperties();
			cleanDisplay();
			windFilm();
		}
		else if ( !isInstalled && wasInstalled )
		{
			isBodyInstalled = false;
			snapshotsLeft = 0;
			
			saveSettings();
		}

		if ( !isInstalled )
		{
			printMainMessage( UIStrings.TEXT_INSTALL_BODY );
		}
	}

	/**
	 * Take a snapshot
	 */
	public void takeSnapshot()
	{
		if ( isBodyInstalled && snapshotsLeft > 0 )
		{
			printMainMessage( UIStrings.TEXT_TAKING_PICTURE );
			
			execute( takeSnapshot );
		}
		else
		{
			printMainMessage( UIStrings.TEXT_OUT_OF_FILM );
		}
	}

	/**
	 * Get current film ISO value
	 * 
	 * @return Current ISO value
	 */
	public int getISO()
	{
		return isoValue;
	}
	

	/**
	 * Get current shutter time
	 * 
	 * @return Shutter time suggested by light meter in milliseconds
	 */
	public long getShutterTime()
	{
		return shutterTime;
	}
	
	/**
	 * Get number of snapshots in currently installed
	 * film
	 * 
	 * @return Number of snapshots in the film
	 */
	public int getFilmSize()
	{
		return filmSize;
	}
	
	/**
	 * Get number of snapshots left
	 * 
	 * @return Number of snapshots left
	 */
	public int getSnapshotsLeft()
	{
		return snapshotsLeft;
	}
	
	/**
	 * Check if camera body is currently installed
	 * 
	 * @return True if camera body is installed
	 */
	public boolean isBodyInstalled()
	{
		return isBodyInstalled;
	}
	
	public void updateCameraStatus()
	{
		if ( !isBodyInstalled )
		{
			printMainMessage( UIStrings.TEXT_BODY_NOT_INSTALLED );
		}
		else if ( snapshotsLeft < 1 )
		{
			printMainMessage( UIStrings.TEXT_OUT_OF_FILM );
		}
		else
		{
			printMainMessage( UIStrings.TEXT_CAMERA_READY );
		}
	}

	/**
	 * Prompt film properties
	 */
	public void promptFilmProperties()
	{
		execute( promptFilmProperties );
	}

	/**
	 * Wind film from primary reel to a secondary reel
	 */
	public void windFilm()
	{
		printMainMessage( UIStrings.TEXT_WINDING_FILM );
		
		execute( windFilm );
	}
	
	/**
	 * Save current camera settings
	 */
	public void saveSettings()
	{
		printMainMessage( UIStrings.TEXT_SAVING );
		
		execute( saveSettings );
	}
	

	public void setFilmProperties(int filmSize, int iso)
	{
		this.filmSize = filmSize;
		this.isoValue = iso;
	}

	public void restoreCameraState(int snapshotsLeft, boolean isBodyInstalled)
	{
		this.snapshotsLeft = snapshotsLeft;
		this.isBodyInstalled = isBodyInstalled;
	}
	
	public void windFilm( int filmNo )
	{
		final int rotationAngle = FilmTransportUtils.calculateRewindAngle( filmNo );
		
		CameraConstants.primaryReelMotor.regulateSpeed( true );
		CameraConstants.primaryReelMotor.setSpeed( CameraConstants.PRIMARY_MOTOR_SPEED  );
		CameraConstants.primaryReelMotor.rotate( (int) CameraConstants.PRIMARY_GEAR_COEFFICIENT*rotationAngle, true );
		
		CameraConstants.secondaryReelMotor.regulateSpeed( true );
		CameraConstants.secondaryReelMotor.setSpeed( CameraConstants.SECONDARY_MOTOR_SPEED );
		CameraConstants.secondaryReelMotor.rotate( (int) CameraConstants.SECONDARY_GEAR_COEFFICIENT*rotationAngle, false );
		
		snapshotsLeft = filmNo;
	}
	
	public void advanceOneFrame()
	{
		final int primartReelRotation = -FilmTransportUtils.calculatePrimaryReelRotationAngle( filmSize - snapshotsLeft, filmSize );
		final int secondaryReelRotation = -FilmTransportUtils.calculateSecondaryReelRotationAngle( filmSize - snapshotsLeft, filmSize );
		
		CameraConstants.primaryReelMotor.regulateSpeed( true );
		CameraConstants.primaryReelMotor.setSpeed( CameraConstants.PRIMARY_MOTOR_SPEED );
		CameraConstants.primaryReelMotor.rotate( (int) CameraConstants.PRIMARY_GEAR_COEFFICIENT*primartReelRotation, true );
		
		CameraConstants.secondaryReelMotor.regulateSpeed( true );
		CameraConstants.secondaryReelMotor.setSpeed( CameraConstants.PRIMARY_MOTOR_SPEED );
		CameraConstants.secondaryReelMotor.rotate( (int) CameraConstants.SECONDARY_GEAR_COEFFICIENT*secondaryReelRotation, false );
		
		snapshotsLeft -= 1;
	}
	
	public void setShutterOpen( final boolean isOpen )
	{
		CameraConstants.shutterMotor.regulateSpeed( false );
		CameraConstants.shutterMotor.smoothAcceleration( true );
		
		if (isOpen )
		{
			CameraConstants.shutterMotor.rotate( SHUTTER_ANGLE, false );
		}
		else
		{
			// Rotate a little more for shutter closed
			CameraConstants.shutterMotor.rotate( -( SHUTTER_ANGLE  + 10), false );
		}
	}
	
	/**
	 * Load camera settings
	 */
	void loadSettings()
	{
		printMainMessage( UIStrings.TEXT_LOADING );

		execute( loadSettings );
	}
	
	public void printMainMessage( final String message )
	{
		LCD.drawString( UIStrings.TEXT_EMPTY, 1, 3 );
		LCD.drawString( message, 1, 3 );
	}
	
	private static void printHeaderData(int ev, final int isoValue)
	{
		LCD.drawString( UIStrings.TEXT_EMPTY, 1, 1 );
		LCD.drawString( UIStrings.TEXT_EV, 1, 1 );
		LCD.drawInt( ev, 4, 1 );
		
		LCD.drawString( UIStrings.TEXT_ISO, 8, 1 );
		LCD.drawInt( isoValue, 12, 1 );
	}
	
	private static void cleanDisplay()
	{
		LCD.clear();
	}
	
	private static void printFooterData( final int fStop, final double time, final int snapshotsLeft )
	{
		LCD.drawString( UIStrings.TEXT_EMPTY, 1, 5 );
		LCD.drawString( UIStrings.TEXT_F, 1, 5 );
		LCD.drawString( UIStrings.TEXT_TIME, 6, 5 );
		LCD.drawString( UIStrings.TEXT_FILM, 13, 5 );
		
		LCD.drawString( UIStrings.TEXT_EMPTY, 1, 6 );
		LCD.drawInt( fStop, 1, 6 );
		LCD.drawInt( (int)Math.round( time ), 6, 6 );
		LCD.drawInt( snapshotsLeft , 13, 6 );
	}

	private static void execute(Runnable runnable)
	{
		runnable.run();
	}
}
